// blinn-phong shading, per-pixel
// 光照计算在像素着色器实现，和phong着色的区别就是它不适用反射向量，而是使用(v+l)向量
// 一是计算简单，二是可以避免r和v夹角大于90°的时候（光源和摄像机在同一方向），表现出来的高光部分被生硬的切掉的情况

#version 450 core

layout(binding = 0) uniform sampler2D mainTex;

in Varyings
{
    vec3 positionWS;
    vec3 normalWS;
    vec2 uv;
} inputs;

out vec4 fragColor;

// 最大支持的additional light数量
const uint ADDITIONAL_LIGHT_MAX_COUNT = 256;

struct Global
{
    vec4 ambient;
};

struct MainLight
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec3 direction;
};

struct AdditionalLight
{
    vec4 ambient[ADDITIONAL_LIGHT_MAX_COUNT];
    vec4 diffuse[ADDITIONAL_LIGHT_MAX_COUNT];
    vec4 specular[ADDITIONAL_LIGHT_MAX_COUNT];
    vec3 position[ADDITIONAL_LIGHT_MAX_COUNT];
    float range[ADDITIONAL_LIGHT_MAX_COUNT];
    vec3 direction[ADDITIONAL_LIGHT_MAX_COUNT];
    float rangeMin[ADDITIONAL_LIGHT_MAX_COUNT];
    float innerCos[ADDITIONAL_LIGHT_MAX_COUNT];
    float outerCos[ADDITIONAL_LIGHT_MAX_COUNT];
};

struct Material
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    float shininess;
};

uniform mat4 objectToWorldMatrix;
uniform mat4 worldToObjectMatrix;
uniform mat4 vpMatrix;
uniform vec3 cameraPos;

uniform Global global;
uniform MainLight mainLight;
uniform AdditionalLight additionalLights;
// 当前场景中有的数量
uniform uint additionalLightCount;
uniform Material material;

struct Light
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec3 direction;
    float attenDis;
    float attenDir;
};

struct Surface
{
    vec3 p;
    vec3 n;
    vec3 v;
};

// 计算表面属性
Surface GetSurface()
{
    Surface surface;
    surface.p = inputs.positionWS;
    surface.n = normalize(inputs.normalWS);
    surface.v = normalize(cameraPos - surface.p);

    return surface;
}

Light GetMainLight(Surface surface)
{
    Light light;
    light.direction = -mainLight.direction;
    light.ambient = mainLight.ambient;
    light.diffuse = mainLight.diffuse;
    light.specular = mainLight.specular;
    light.attenDis = 1.0;
    light.attenDir = 1.0;
    return light;
}

Light GetAdditionalLight(uint index, Surface surface, Material material)
{
    Light light;
    light.ambient = additionalLights.ambient[index];
    light.diffuse = additionalLights.diffuse[index];
    light.specular = additionalLights.specular[index];
    light.direction = normalize(additionalLights.position[index] - surface.p);
    // 注意，光源要考虑衰减，衰减分为距离衰减（随距离增加，亮度降低）和方向衰减（在某些方向上亮度降低）
    // 距离衰减：平方衰减，在指定距离的80%后的部分线性衰减到0（比如point light和spot light）
    // 1 / (r * r)是标准的平方衰减，但实际中，会导致衰减的太过厉害，因此需要将1替换为一个rMin，表示
    // 初始衰减距离，也就是说，大于这个距离的才开始进行衰减，避免衰减过于剧烈
    // 最后要clamp到[0,1]避免距离小的时候亮度太高（就好像光源有个体积一样，无法无限逼近光源中心）
    // atten_qua = 1 / (r * r) 可替换为atten_qua = (rMin * rMin) / (r * r)
    // atten_li = (r_max * r_max - r * r) / (r_max * r_max - 0.8 * 0.8 * r_max * r_max)(这个计算可以优化为MAD指令，写成a*b+c的形式）
    // atten_dis = atten_qua * clamp(atten_li, 0.0, 1.0)
    // L_d = L_d * atten_dis
    float r2 = dot(additionalLights.position[index] - surface.p, additionalLights.position[index] - surface.p);
    float rMin2 = additionalLights.rangeMin[index] * additionalLights.rangeMin[index];
    float rMax2 = additionalLights.range[index] * additionalLights.range[index];
    float rAttenLi2 = 0.8 * 0.8 * rMax2;
    float atten_qua = clamp(rMin2 / r2, 0.0, 1.0);
    float atten_li = (rMax2 - r2) / (rMax2 - rAttenLi2);
    light.attenDis = atten_qua * clamp(atten_li, 0.0, 1.0);
    // 方向衰减，主要用于spot light里，spot light照明是一个
    // 圆锥范围，存在内角和外角，内角范围内，不衰减，内角到外角范围内，线性衰减，外角范围以外，衰减为0
    // 因此可以类似距离衰减公式
    // atten_dir = (cosTheta - cosOuter) / (consInner - cosOuter)(这个计算可以优化为MAD指令，写成a*b+c的形式）
    float cosTheta = dot(additionalLights.direction[index], -light.direction);
    light.attenDir = clamp((cosTheta - additionalLights.outerCos[index]) / (additionalLights.innerCos[index] - additionalLights.outerCos[index]), 0.0, 1.0);

    return light;
}

vec4 CalcADS(Light light, Surface surface, Material material)
{
    // ambient: L_a * M_a
    // L_a = L_a
    vec4 ambient = light.ambient * material.ambient;
    // diffuse: L_d * M_d * max(0, dot(n, l))
    vec4 diffuse = light.diffuse * material.diffuse * light.attenDis * light.attenDir * max(0.0, dot(surface.n, light.direction));
    // specular: h = normalize(l + v)
    //           L_s * M_s * max(0, pow(dot(n, h), M_shininess))
    vec3 h = normalize(light.direction + surface.v);
    vec4 specular = light.specular * material.specular * light.attenDis * light.attenDir * pow(max(0.0, dot(h, surface.n)), material.shininess);

    return ambient + diffuse + specular;
}

void main()
{
    // 计算表面属性
    Surface surface = GetSurface();

    // 实现ADL光照模型（世界坐标系）
    // global
    vec4 globalAmbient = global.ambient * material.ambient;

    // 为了方便支持多光源，光源分为两种类型，一种是MainLight，另一种是AdditionalLight，MainLight就是DirectionLight，AdditionLight
    // 包括PointLight和SpotLight等带有位置、衰减的光源
    // 这两种类型需要分别计算

    // 1. 计算MainLight
    Light mainLight = GetMainLight(surface);
    vec4 lightColor = CalcADS(mainLight, surface, material);

    // 2. 计算AdditionalLights
    for(uint i = 0; i < min(additionalLightCount, ADDITIONAL_LIGHT_MAX_COUNT); ++i)
    {
        Light light = GetAdditionalLight(i, surface, material);
        lightColor += CalcADS(light, surface, material);
    }

    // 最终颜色
    vec4 texColor = texture(mainTex, inputs.uv);
    fragColor = texColor * (globalAmbient + lightColor);
}
